{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "m7hbib3bSGO9"
      },
      "source": [
        "**Copyright 2020 The TensorFlow Authors.**"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "mEE8NFIMSGO-"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SyiSRgdtSGPC"
      },
      "source": [
        "# Keras 예제의 가중치 클러스터링"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kW3os956SGPD"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/model_optimization/guide/clustering/clustering_example\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/model_optimization/guide/clustering/clustering_example.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Run in Google Colab</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/model_optimization/guide/clustering/clustering_example.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">View source on GitHub</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/model_optimization/guide/clustering/clustering_example.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드하기</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dKnJyAaASGPD"
      },
      "source": [
        "## 개요\n",
        "\n",
        "TensorFlow 모델 최적화 도구 키트의 일부인 *가중치 클러스터링*에 대한 엔드 투 엔드 예제를 소개합니다.\n",
        "\n",
        "### 기타 페이지\n",
        "\n",
        "가중치 클러스터링에 대한 소개와 이를 사용해야 하는지 여부(지원 내용 포함)를 결정하려면 [개요 페이지](https://www.tensorflow.org/model_optimization/guide/clustering)를 참조하세요.\n",
        "\n",
        "16개의 클러스터로 모델을 완전하게 클러스터링하는 등 해당 사용 사례에 필요한 API를 빠르게 찾으려면 [종합 가이드](https://www.tensorflow.org/model_optimization/guide/clustering/clustering_comprehensive_guide)를 참조하세요.\n",
        "\n",
        "### 내용\n",
        "\n",
        "이 튜토리얼에서는 다음을 수행합니다.\n",
        "\n",
        "1. MNIST 데이터세트를 위한 `tf.keras` 모델을 처음부터 훈련합니다.\n",
        "2. 가중치 클러스터링 API를 적용하여 모델을 미세 조정하고 정확성을 확인합니다.\n",
        "3. 클러스터링으로부터 6배 더 작은 TF 및 TFLite 모델을 만듭니다.\n",
        "4. 가중치 클러스터링과 훈련 후 양자화를 결합하여 8배 더 작은 TFLite 모델을 만듭니다.\n",
        "5. TF에서 TFLite로 정확성이 지속되는지 확인합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RgcQznnZSGPE"
      },
      "source": [
        "## 설정\n",
        "\n",
        "이 Jupyter 노트북은 로컬 [virtualenv](https://www.tensorflow.org/install/pip?lang=python3#2.-create-a-virtual-environment-recommended) 또는 [colab](https://colab.sandbox.google.com/)에서 실행할 수 있습니다. 종속성 설정에 대한 자세한 내용은 [설치 가이드](https://www.tensorflow.org/model_optimization/guide/install)를 참조하세요. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3asgXMqnSGPE"
      },
      "outputs": [],
      "source": [
        "! pip install -q tensorflow-model-optimization"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gL6JiLXkSGPI"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "from tensorflow import keras\n",
        "\n",
        "import numpy as np\n",
        "import tempfile\n",
        "import zipfile\n",
        "import os"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dKzOfl5FSGPL"
      },
      "source": [
        "## 클러스터링을 사용하지 않고 MNIST용 tf.keras 모델 훈련하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "w7Fd6jZ7SGPL"
      },
      "outputs": [],
      "source": [
        "# Load MNIST dataset\n",
        "mnist = keras.datasets.mnist\n",
        "(train_images, train_labels), (test_images, test_labels) = mnist.load_data()\n",
        "\n",
        "# Normalize the input image so that each pixel value is between 0 to 1.\n",
        "train_images = train_images / 255.0\n",
        "test_images  = test_images / 255.0\n",
        "\n",
        "# Define the model architecture.\n",
        "model = keras.Sequential([\n",
        "    keras.layers.InputLayer(input_shape=(28, 28)),\n",
        "    keras.layers.Reshape(target_shape=(28, 28, 1)),\n",
        "    keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation=tf.nn.relu),\n",
        "    keras.layers.MaxPooling2D(pool_size=(2, 2)),\n",
        "    keras.layers.Flatten(),\n",
        "    keras.layers.Dense(10)\n",
        "])\n",
        "\n",
        "# Train the digit classification model\n",
        "model.compile(optimizer='adam',\n",
        "              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "              metrics=['accuracy'])\n",
        "\n",
        "model.fit(\n",
        "    train_images,\n",
        "    train_labels,\n",
        "    validation_split=0.1,\n",
        "    epochs=10\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rBOQ8MeESGPO"
      },
      "source": [
        "### 기준 모델을 평가하고 나중에 사용할 수 있도록 저장하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HYulekocSGPP"
      },
      "outputs": [],
      "source": [
        "_, baseline_model_accuracy = model.evaluate(\n",
        "    test_images, test_labels, verbose=0)\n",
        "\n",
        "print('Baseline test accuracy:', baseline_model_accuracy)\n",
        "\n",
        "_, keras_file = tempfile.mkstemp('.h5')\n",
        "print('Saving model to: ', keras_file)\n",
        "tf.keras.models.save_model(model, keras_file, include_optimizer=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cWPgcnjKSGPR"
      },
      "source": [
        "## 클러스터링을 사용하여 사전 훈련된 모델 미세 조정하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Y2wKK7w9SGPS"
      },
      "source": [
        "사전 훈련된 전체 모델에 `cluster_weights()` API를 적용하여 압축 후 적절한 정확성을 유지하면서 모델 크기가 줄어드는 효과를 입증합니다. 해당 사용 사례에서 정확성과 압축률의 균형을 가장 잘 유지하는 방법은 [포괄적 가이드](https://www.tensorflow.org/model_optimization/guide/clustering/clustering_comprehensive_guide)의 레이어별 예를 참조하세요.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ea40z522SGPT"
      },
      "source": [
        "### 모델 정의 및 클러스터링 API 적용하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7aOB5vjOZMTS"
      },
      "source": [
        "모델을 클러스터링 API로 전달하기 전에 모델이 훈련되었고 수용 가능한 정확성을 보이는지 확인합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OzqKKt0mSGPT"
      },
      "outputs": [],
      "source": [
        "import tensorflow_model_optimization as tfmot\n",
        "\n",
        "cluster_weights = tfmot.clustering.keras.cluster_weights\n",
        "CentroidInitialization = tfmot.clustering.keras.CentroidInitialization\n",
        "\n",
        "clustering_params = {\n",
        "  'number_of_clusters': 16,\n",
        "  'cluster_centroids_init': CentroidInitialization.LINEAR\n",
        "}\n",
        "\n",
        "# Cluster a whole model\n",
        "clustered_model = cluster_weights(model, **clustering_params)\n",
        "\n",
        "# Use smaller learning rate for fine-tuning clustered model\n",
        "opt = tf.keras.optimizers.Adam(learning_rate=1e-5)\n",
        "\n",
        "clustered_model.compile(\n",
        "  loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "  optimizer=opt,\n",
        "  metrics=['accuracy'])\n",
        "\n",
        "clustered_model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ev4MyClmSGPW"
      },
      "source": [
        "### 모델을 미세 조정하고 기준 대비 정확성 평가하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vQoy9CcASGPX"
      },
      "source": [
        "하나의 epoch 동안 클러스터링이 있는 모델을 미세 조정합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jn29-coXSGPX"
      },
      "outputs": [],
      "source": [
        "# Fine-tune model\n",
        "clustered_model.fit(\n",
        "  train_images,\n",
        "  train_labels,\n",
        "  batch_size=500,\n",
        "  epochs=1,\n",
        "  validation_split=0.1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dvaZKoxtTORx"
      },
      "source": [
        "이 예의 경우, 기준과 비교하여 클러스터링 후 테스트 정확성의 손실이 미미합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bE7MxpWLTaQ1"
      },
      "outputs": [],
      "source": [
        "_, clustered_model_accuracy = clustered_model.evaluate(\n",
        "  test_images, test_labels, verbose=0)\n",
        "\n",
        "print('Baseline test accuracy:', baseline_model_accuracy)\n",
        "print('Clustered test accuracy:', clustered_model_accuracy)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VXfPMa6ISGPd"
      },
      "source": [
        "## 클러스터링으로부터 **6배** 더 작은 모델 만들기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1zr_QIhcUeuC"
      },
      "source": [
        "클러스터링의 압축 이점을 확인하려면 `strip_clustering`과 표준 압축 알고리즘(예: gzip 이용) 적용이 모두 필요합니다.\n",
        "\n",
        "먼저, TensorFlow를 위한 압축 가능한 모델을 만듭니다. 여기서, `strip_clustering`은 훈련 중에만 클러스터링에 필요한 모든 변수(예: 클러스터 중심과 인덱스를 저장하기 위한 `tf.Variable`)를 제거합니다. 이러한 변수를 제거하지 않으면 추론 중에 모델 크기가 증가하게 됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4h6tSvMzSGPd"
      },
      "outputs": [],
      "source": [
        "final_model = tfmot.clustering.keras.strip_clustering(clustered_model)\n",
        "\n",
        "_, clustered_keras_file = tempfile.mkstemp('.h5')\n",
        "print('Saving clustered model to: ', clustered_keras_file)\n",
        "tf.keras.models.save_model(final_model, clustered_keras_file, \n",
        "                           include_optimizer=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jZcotzPSVBtu"
      },
      "source": [
        "그런 다음, TFLite를 위한 압축 가능한 모델을 만듭니다. 클러스터링된 모델을 대상 백엔드에서 실행 가능한 형식으로 변환할 수 있습니다. TensorFlow Lite는 모바일 기기에 배포하는 데 사용할 수 있는 예입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "v2N47QW6SGPh"
      },
      "outputs": [],
      "source": [
        "clustered_tflite_file = '/tmp/clustered_mnist.tflite'\n",
        "converter = tf.lite.TFLiteConverter.from_keras_model(final_model)\n",
        "tflite_clustered_model = converter.convert()\n",
        "with open(clustered_tflite_file, 'wb') as f:\n",
        "  f.write(tflite_clustered_model)\n",
        "print('Saved clustered TFLite model to:', clustered_tflite_file)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "S7amG_9XV-w9"
      },
      "source": [
        "실제로 gzip을 통해 모델을 압축하는 도우미 함수를 정의하고 압축된 크기를 측정합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1XJ4QBMpW5JB"
      },
      "outputs": [],
      "source": [
        "def get_gzipped_model_size(file):\n",
        "  # It returns the size of the gzipped model in bytes.\n",
        "  import os\n",
        "  import zipfile\n",
        "\n",
        "  _, zipped_file = tempfile.mkstemp('.zip')\n",
        "  with zipfile.ZipFile(zipped_file, 'w', compression=zipfile.ZIP_DEFLATED) as f:\n",
        "    f.write(file)\n",
        "\n",
        "  return os.path.getsize(zipped_file)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "INeAOWRBSGPj"
      },
      "source": [
        "클러스터링으로부터 모델이 **6배** 더 작아진 것을 확인하세요."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "SG1MgZCeSGPk"
      },
      "outputs": [],
      "source": [
        "print(\"Size of gzipped baseline Keras model: %.2f bytes\" % (get_gzipped_model_size(keras_file)))\n",
        "print(\"Size of gzipped clustered Keras model: %.2f bytes\" % (get_gzipped_model_size(clustered_keras_file)))\n",
        "print(\"Size of gzipped clustered TFlite model: %.2f bytes\" % (get_gzipped_model_size(clustered_tflite_file)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5TOgpEGfSGPn"
      },
      "source": [
        "## 가중치 클러스터링과 훈련 후 양자화를 결합하여 **8배** 더 작은 TFLite 모델 만들기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BQb50aC3SGPn"
      },
      "source": [
        "추가적인 이점을 얻기 위해 클러스터링한 모델에 훈련 후 양자화를 적용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "XyHC8euLSGPo"
      },
      "outputs": [],
      "source": [
        "converter = tf.lite.TFLiteConverter.from_keras_model(final_model)\n",
        "converter.optimizations = [tf.lite.Optimize.DEFAULT]\n",
        "tflite_quant_model = converter.convert()\n",
        "\n",
        "_, quantized_and_clustered_tflite_file = tempfile.mkstemp('.tflite')\n",
        "\n",
        "with open(quantized_and_clustered_tflite_file, 'wb') as f:\n",
        "  f.write(tflite_quant_model)\n",
        "\n",
        "print('Saved quantized and clustered TFLite model to:', quantized_and_clustered_tflite_file)\n",
        "print(\"Size of gzipped baseline Keras model: %.2f bytes\" % (get_gzipped_model_size(keras_file)))\n",
        "print(\"Size of gzipped clustered and quantized TFlite model: %.2f bytes\" % (get_gzipped_model_size(quantized_and_clustered_tflite_file)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "U-yBcocGSGPv"
      },
      "source": [
        "## TF에서 TFLite로 정확성이 지속되는지 확인하기"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Jh_pcf0XSGPv"
      },
      "source": [
        "테스트 데이터세트에서 TFLite 모델을 평가하는 도우미 함수를 정의합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EJ9B7pRISGPw"
      },
      "outputs": [],
      "source": [
        "def eval_model(interpreter):\n",
        "  input_index = interpreter.get_input_details()[0][\"index\"]\n",
        "  output_index = interpreter.get_output_details()[0][\"index\"]\n",
        "\n",
        "  # Run predictions on every image in the \"test\" dataset.\n",
        "  prediction_digits = []\n",
        "  for i, test_image in enumerate(test_images):\n",
        "    if i % 1000 == 0:\n",
        "      print('Evaluated on {n} results so far.'.format(n=i))\n",
        "    # Pre-processing: add batch dimension and convert to float32 to match with\n",
        "    # the model's input data format.\n",
        "    test_image = np.expand_dims(test_image, axis=0).astype(np.float32)\n",
        "    interpreter.set_tensor(input_index, test_image)\n",
        "\n",
        "    # Run inference.\n",
        "    interpreter.invoke()\n",
        "\n",
        "    # Post-processing: remove batch dimension and find the digit with highest\n",
        "    # probability.\n",
        "    output = interpreter.tensor(output_index)\n",
        "    digit = np.argmax(output()[0])\n",
        "    prediction_digits.append(digit)\n",
        "\n",
        "  print('\\n')\n",
        "  # Compare prediction results with ground truth labels to calculate accuracy.\n",
        "  prediction_digits = np.array(prediction_digits)\n",
        "  accuracy = (prediction_digits == test_labels).mean()\n",
        "  return accuracy"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0swuxbPmSGPy"
      },
      "source": [
        "클러스터링되고 양자화된 모델을 평가한 다음, TensorFlow의 정확성이 TFLite 백엔드까지 유지되는지 확인합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "RFD4LXjpSGPz"
      },
      "outputs": [],
      "source": [
        "interpreter = tf.lite.Interpreter(model_content=tflite_quant_model)\n",
        "interpreter.allocate_tensors()\n",
        "\n",
        "test_accuracy = eval_model(interpreter)\n",
        "\n",
        "print('Clustered and quantized TFLite test_accuracy:', test_accuracy)\n",
        "print('Clustered TF test accuracy:', clustered_model_accuracy)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JgXTEXC7SGP1"
      },
      "source": [
        "## 결론"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7JhbpowqSGP1"
      },
      "source": [
        "이 튜토리얼에서는 TensorFlow 모델 최적화 도구 키트 API를 사용하여 클러스터링된 모델을 만드는 방법을 알아보았습니다. 구체적으로, 정확성 차이를 최소화하면서 8배 더 작은 MNIST용 모델을 생성하기 위한 엔드 투 엔드 예제를 살펴보았습니다. 리소스가 제한된 환경에서 배포할 때 특히 중요할 수 있는 이 새로운 기능을 한 번 사용해 보세요.\n"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "clustering_example.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
